package com.ls.fw.search.solrj.service;


import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ls.fw.search.solrj.exception.SearchException;
import com.ls.fw.search.solrj.support.builders.FullTextQueryStringBuilder;
import com.ls.fw.search.solrj.support.builders.SearchResponseBuilder;
import com.ls.fw.search.solrj.support.builders.SolrQueryBuilder;
import com.ls.fw.search.solrj.support.search.FacetedSearchRequest;
import com.ls.fw.search.solrj.support.search.SearchParameter;
import com.ls.fw.search.solrj.support.search.SearchResponse;


public class SolrSearchService<T, P extends Enum<?> & SearchParameter, ST extends T, R extends FacetedSearchRequest<P>>
    implements SearchService<T, P, R> {

  private static final Logger LOG = LoggerFactory.getLogger(SolrSearchService.class);

  /*
   * Solr server instance, this abstract type is used because it can hold instance of:
   * CommonsHttpSolrServer or EmbeddedSolrServer.
   */
  private final SolrClient solrClient;

  // Cached instance of response builder.
  // This instance is used for cloning subsequent usages of this object without the penalty recreate similar instances.
  private final SearchResponseBuilder<T, ST, P> responseBuilder;

  private final SolrQueryBuilder<ST, P> searchQueryBuilder;

  /**
   * Default constructor.
   *
   * @param solrClient Solr server instance, this abstract type is used because it can hold instance of
   *        CommonsHttpSolrServer or EmbeddedSolrServer
   * @param type of the results content
   * @param solrType Solr response type
   * @param enumSearchParamType type of the enum search parameter
   * @param primarySortOrder ordered fields used for an optional sort order in every search
   */
  public SolrSearchService(SolrClient solrClient, Class<T> type, Class<ST> solrType,
                           Class<P> enumSearchParamType, Map<String, SolrQuery.ORDER> primarySortOrder) {
    this.solrClient = solrClient;
    FullTextQueryStringBuilder fullTextQueryBuilder = FullTextQueryStringBuilder.create(solrType);
    responseBuilder = SearchResponseBuilder.create(type, solrType, enumSearchParamType);
    responseBuilder.withHighlightFields(fullTextQueryBuilder.getHighlightedFields());
    searchQueryBuilder = SolrQueryBuilder.create(enumSearchParamType, solrType);
    searchQueryBuilder.withQueryBuilder(fullTextQueryBuilder);
    searchQueryBuilder.withPrimarySortOrder(new LinkedHashMap<String, SolrQuery.ORDER>(primarySortOrder));
  }

  public SolrSearchService(SolrClient solrClient, Class<T> type, Class<ST> solrType,
                           Class<P> enumSearchParamType) {
    this.solrClient = solrClient;
    FullTextQueryStringBuilder fullTextQueryBuilder = FullTextQueryStringBuilder.create(solrType);
    responseBuilder = SearchResponseBuilder.create(type, solrType, enumSearchParamType);
    responseBuilder.withHighlightFields(fullTextQueryBuilder.getHighlightedFields());
    searchQueryBuilder = SolrQueryBuilder.create(enumSearchParamType, solrType);
    searchQueryBuilder.withQueryBuilder(fullTextQueryBuilder);
  }

  /**
   * Full constructor.
   *
   * @param solrClient Solr client instance, this abstract type is used because it can hold instance of
   *        CommonsHttpSolrServer or EmbeddedSolrServer
   * @param requestHandler specific Solr request handler to be used
   * @param type of the results content
   * @param primarySortOrder ordered fields used for an optional sort order in every search
   */
  public SolrSearchService(SolrClient solrClient,   final String requestHandler, Class<T> type,
                           Class<ST> solrType, Class<P> enumSearchParamType,
                           Map<String,SolrQuery.ORDER> primarySortOrder) {
    this(solrClient, type, solrType, enumSearchParamType, primarySortOrder);
    searchQueryBuilder.withRequestHandler(requestHandler);
  }


  /**
   * Issues a SolrQuery and converts the response to a SearchResponse object. Besides, the facets and paging
   * parameter and responses are handled in the request and response objects.
   *
   * @param searchRequest the searchRequest that contains the search parameters
   * @return the SearchResponse of the search operation
   */
  @Override
  public SearchResponse<T, P> search(final R searchRequest) {

    try {
      // Defensive copy: done because the build method is not thread safe.
      SolrQueryBuilder<ST, P> requestBuilder = searchQueryBuilder.getCopy();
      SolrQuery solrQuery = requestBuilder.build(searchRequest);

      // Executes the search operation in Solr
      LOG.debug("Solr query executed: {}", solrQuery);
      final QueryResponse queryResponse = solrClient.query(solrQuery);

      // Defensive copy: done because the build method is not thread safe.
      return responseBuilder.getCopy().build(searchRequest, queryResponse);
    } catch (SolrServerException e) {
      if (e.getRootCause() instanceof IllegalArgumentException) {
        LOG.error("Bad search request", e);
        throw (IllegalArgumentException) e.getRootCause();
      } else {
        LOG.error("Error executing the search operation", e);
        throw new SearchException(e);
      }
    } catch (IOException e) {
      LOG.error("Error executing the search operation", e);
      throw new SearchException(e);
    }
  }

  /**
   * @return the responseBuilderBase
   */
  protected SearchResponseBuilder<T, ST, P> getResponseBuilder() {
    return responseBuilder;
  }


  /**
   * @return the solrClient
   */
  protected SolrClient getSolrClient() {
    return solrClient;
  }
}
